Skip to content
Advertisement

React redux, Uncaught TypeError: Cannot assign to read only property ‘current’ of object ‘#’

I am making a website to modify db data. first, The structure of the component is as follows

<Contents />
 <Table />
  <Row />
   <Column />
    <Input />

When the row component is created,creating the ref of the input component and redux manages it.

const StyledRow = styled.div`
    text-align:center;
    display:flex;
    align-items:center;
`;


const DeleteButton = styled(Button)`
    background-color: #ff7787;
    margin-right:5px;
    color:white;
    width:40px;
    
    ${({top}) => top && css`
        background-color:white;
        color:white;
        width:40px;
    `}
`;

function Row({top, rowId}){

    const dispatch = useDispatch();
    const columns = useMemo(() => columnPhoneInfo,[]);

    const inputsRef = useMemo(()=>
        !top && 
            Array(8).fill(0).map(() => createRef()
    ),[]);
    // const inputsRef = useRef([]);

    useEffect(()=> {
        // console.log(rowId,top);
        !top &&
            dispatch(phoneDataAddRef(rowId,inputsRef));
    },[]);

    const handleDeleteButton = useCallback( (id) => {
            dispatch(phoneDataUpdate.Delete(id));
    },[]);

    if( top ) return(
        <StyledRow>
            <DeleteButton top/>
            {columns.map((column)=>
                <Column  key={`head_${column.name}`} width={column.width} top>
                    {column.name}
                </Column>
            )}
        </StyledRow>

    );
    return( 
        <StyledRow>
            <DeleteButton onClick={()=>handleDeleteButton(rowId)}> delete </DeleteButton>
            {columns.map((column, index)=>
                <Column key={`row_${rowId}_${column.name}`} width={column.width} textalign={column.textalign}>
                    <Input  ref={inputsRef[index] } colIndex={index} id={rowId} column={column} />
                    {/* <Input colIndex={index} id={rowId} column={column} /> */}
                </Column>   
            )}
        </StyledRow>
    );
}
export default React.memo(Row);

Input component only receives ref as forwardRef

const StyledInput = styled.input`
    ${({ width, textalign })=>css`
        width:${width};
        text-align:${textalign};
    `}
`;


const Input = forwardRef(({colIndex, id},inputRef) =>{
    const dispatch = useDispatch();
    const didShowAlert = useRef(false);

    const nowColumnInfo = columnPhoneInfo[colIndex];
    const nowColumnValidCheck = inputValidCheck[colIndex];
    
    const { nowVal, firstVal, isAddedRow } = useSelector(state =>({
        nowVal     : state.phoneData.data.rows.find(val=>val.id === id)[nowColumnInfo.colname],
        firstVal   : state.phoneData.firstData.lastId < id 
                     ? null
                     : state.phoneData.firstData.rows.find(val=>val.id===id)[nowColumnInfo.colname],
        isAddedRow : state.phoneData.firstData.lastId < id
                     ? true
                     : false,
    }),shallowEqual);

    const callbackDispatch = useCallback((dispatchFunc) =>{
        return(...args)=>{
            dispatch(dispatchFunc(...args));
        }
    },[dispatch]);

    //////////////////////
    const inputChange = useCallback( (value) => 
        dispatch(phoneDataUpdate.Change(id,nowColumnInfo.colname, value))
    ,[nowColumnInfo.colname, dispatch, id]);
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    const updateListChange = callbackDispatch(phoneDataUpdateList.Change);
    const updateListDelete = callbackDispatch(phoneDataUpdateList.Delete);
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    const handleChange = useCallback( (e) => {
        // ... todo handle change
    },[]);

     ///////////////////////////////////////////////////////// 
    const handleBlur = useCallback( (e) =>{
        // ... todo handle blur
    },[]);

    return( 
        <StyledInput 
            textalign={nowColumnInfo.textalign} 
            width={nowColumnInfo.width} 
            value={nowVal === null ? '': nowVal }
            onChange={handleChange}
            onBlur={handleBlur}
            ref={inputRef}
            // placeholder={}
        />
    );
});
export default React.memo(Input);

And finally, the redux module

////////////////////////////////////////////////////////
const PHONE_DATA_DELETE = 'phoneData/PHONE_DATA_DELETE';
////////////////////////////////////////////////////////
const PHONE_DATA_ADD_REF = 'phoneData/PHONE_DATA_ADD_REF';
////////////////////////////////////////////////////////

const dataInitRow = {
    id:null,
    model_name:null,
    machine_name:null,
    shipping_price:null,
    maker:null,
    created:null,
    battery:null,
    screen_size:null,
    storage:null,
};

const dataInit = {
    lastId:null,
    rows:[],
}

const initialState = {
    state:{
        loading:false,
        error:false,
    },
    data:dataInit,
    refData:[],
    firstData:dataInit,
    dataChangeList:{
        dataAddList:[],
        dataDeleteList:[],
        dataUpdateList:[],
    },
};



const phoneDataFetchAsync = createPromiseThunk(PHONE_DATA, restAPI.getAllPhoneInfo);
////////////////////////////////////////////////////////
const phoneDataAddRef=(id, ref) =>({
    type:PHONE_DATA_ADD_REF,
    id:id,
    ref:ref,
});

const phoneDataUpdateList = ({
    Change:(id,colName, value) => ({
        type:PHONE_DATA_UPDATE_LIST_CHANGE,
        id: id,
        colName: colName,
        value: value,
    }),
    Delete:(id, colName) => ({
        type:PHONE_DATA_UPDATE_LIST_DELETE,
        id: id,
    }),
});

////////////////////////////////////////////////////////

export default function phoneData(state = initialState, action){
    // console.log(`add: ${state.dataChangeList.dataAddList}, delete: ${state.dataChangeList.dataDeleteList}, change: ${state.dataChangeList.dataUpdateList}`);
    switch(action.type)
        case PHONE_DATA_DELETE:
            return produce(state, draft=>{
                console.log(action);

                const idx = state.dataChangeList.dataAddList.findIndex( val => val === action.id);
                if(  idx === -1 )
                    draft.dataChangeList.dataDeleteList.push(action.id);
                else
                    draft.dataChangeList.dataAddList.splice(idx,1);


                draft.refData = state.refData.filter(row => row.id !== action.id);
                draft.data.rows = state.data.rows.filter(row =>row.id !== action.id);
            });
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
        case PHONE_DATA_ADD_REF:
            return produce(state, draft=>{
                draft.refData.push({id:action.id, refs:action.ref});     
            });
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
        default:
            return state;
    }
}




export {phoneDataFetchAsync,
        phoneDataDelete,,
        phoneDataAddRef,
        };

The problem area is the delete button. When I press the button, that error comes out. But if don’t add ref to state, no error occurs. or even if I comment out the bottom part, there is no error.

draft.data.rows = state.data.rows.filter(row =>row.id !== action.id);

Or comment out the bottom part

draft.refData.push({id:action.id, refs:action.ref});

I’ve been trying to fix it all day today, but I don’t know what’s wrong. How can I fix it?

Advertisement

Answer

Don’t Store Refs in Redux

What you are attempting to do here violates not one but two of the “Essential” rules of Redux.

  1. Mutation of state

Ref objects are mutable by design. The reference changes by updating the .current property of the ref object, but the object instance stays the same. This violates Redux rules that state must be immutable because the Redux state can be mutated by unrelated React code.

  1. Non-Serializable

All data in your Redux store should be able to be converted to a JSON string and back. Anything that would get lost in that conversion does not belong in Redux. An instance of a DOM element is not serializable.

You need to figure out what raw data is needed to represent your state. Store that data in Redux and use it to generate the correct DOM elements. Storing the DOM in Redux is doing it backwards.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement