What I'm trying to do
I have a main component with an array of "profiles". For each profile, I have two different CONDITIONAL components (only one is shown at once). Each of these two components has a button that is supposed to switch the components when clicked. So I have lifted the state up to the main component, created component state using the 'useState' hook (which is an array, where each index is a string representing the child component to be shown for each element in the profiles array), I've created two functions for event handling for these clicks, and passed them into their child components as render props.
They start out as the first child component.
The Problem & How I found it
When you press the button to switch to the other component, it works. When you press the button to go back, it crashes. Saying "TypeError: Cannot assign to read-only property '0' of string 'large'". I put some console.log(state) after the useState initialization, and after the state change calls in each function. What happens (with a test list with just one element) is that
- When the component initializes, the state is shown as ['normal'] (original state, good)
- When button of first component is clicked, ['normal'] becomes ['large'] (as intended)
- When a component is now re-rendered, the state becomes 'large' (no longer an array)
- When the button of second component is clicked, the app crashes cause it couldn't change the array element casue it's no longer an array
Main Component
const Peers = props => {
let dummyPeer = {
_id: "9asdf98sj3942j4fs9ji",
user: {
name: "Test Peer",
avatar: "//www.gravatar.com/avatar/cd56136f6d9abfdf4a0198dc9ce656c8?s=200&r=pg&d=mm"
},
bio: "Biography for Test Peer",
year: "2022",
courses: [
"CISC124",
"PSYC223",
"PSYC236",
"COMM200",
"CISC251"
]
}
let profiles = [];
profiles.push(dummyPeer);
let initialState = [];
profiles.forEach(profile => {
initialState.push("normal");
});
let [viewState, setViewState] = useState(initialState);
console.log(viewState);
const openLargeView = (id) => {
let changeIndex = profiles.map(profile => profile._id).indexOf(id);
setViewState(state => state[changeIndex] = "large");
console.log(viewState);
}
const closeLargeView = (id) => {
let changeIndex = profiles.map(profile => profile._id).indexOf(id);
setViewState(state => state[changeIndex] = "normal");
console.log(viewState);
}
return (
<Fragment>
{profiles.map((profile, index) => (<Fragment key={profile._id} >
{viewState[index] === "normal" ? (
<Peer openLargeView={openLargeView} profile={profile} />
) : (
<ViewPeer closeLargeView={closeLargeView} profile={profile} />
)}
</Fragment>))}
</Fragment>
)
}
Child Component 1:
const Peer = ({ profile, openLargeView }) => {
const { _id, user, bio, year, courses } = profile;
const { avatar } = user;
return (<Fragment>
<div className="card-row">
<div className="profile-header">
<h1 className="peer-text row-title"> {user.name} </h1>
<p className="peer-text peer-small"> {year} </p>
<img className="avatar avatar-peer-small" src={avatar} alt='' />
</div>
<button onClick={() => openLargeView(_id)} className="btn-small"> More </button>
</div>
</Fragment>)
}
Child Component 2:
const ViewPeer = ({ profile, closeLargeView }) => {
const { _id, user, bio, year, courses } = profile;
const { avatar } = user;
let courseElements = courses.map((course, index) =>
<li key={index} className="profile-text"> {course} </li>
);
return (
<Fragment>
<div className="card-md peer-card">
<div className="profile-header">
<h1 className="peer-text"> {user.name} </h1>
<img className="avatar avatar-peer" src={avatar} alt='' />
</div>
<div className="profile-info">
<h2 className="profile-text"> {bio} </h2>
<h2 className="profile-text2"> Year: {year} </h2>
<ul className="course-list"> {courseElements} </ul>
<div className="profile-button-group">
<button onClick={() => closeLargeView(_id)} className="btn-small"> Close </button>
<button className="btn-small"> Send Buddy Request </button>
</div>
</div>
</div>
</Fragment>
)
}
Expected and actual results
I expect it to go back to the original component when the button of the first component is clicked but the state turned into an array to a string and the app crashes.