MUI tabs working but not animating changes - reactjs

I have a React app that displays a list of cards displaying assignment info for an educational app. Each card contains MUI tabs so that teachers can navigate through students' work. Simplified code below:
teacherClass.jsx
function TeacherClass(props) {
const [classData, setClassData] = React.useState(props.classData);
const [assignments, setAssignments] = React.useState([]);
//assignments are found from Firebase elsewhere in this file. Omitted for simplicity.
const [tabValue, setTabValue] = React.useState(0);
const handleTabChange = (event, newValue) => {
setTabValue(newValue);
}
const AssignmentCard = (assignment) => (
<Card>
<Tabs value={tabValue} onChange={handleTabChange} >
{classData.students ? classData.students.map((stu) =>
<Tab label={stu.name} value={stu} />) : null }
</Tabs>
</Card>
)
return (
<div>
{ assignments ? assignments.map((assignment) => AssignmentCard(assignment)) : null }
</div>
);
}
export default TeacherClass;
This works just fine, however the problem is that when I change tabs, it changes the tab for EVERY card, not just the one that I selected. I thought I could fix this by having my tabValue useState as an array or something, but instead decided to create a new component so that each card could have its own useState:
teacherAssignmentCard.jsx
function TeacherAssignmentCard(props) {
const classData = props.classData;
const assignment = props.assignmentData;
const [tabValue, setTabValue] = React.useState(classData.students[0]);
const AssignmentCard = () => {Same code as 'AssignmentCard' in 'teacherClass.jsx',
but with no argument as 'assignment' comes from props}
return (
<AssignmentCard />
);
and in teacherClass.jsx I of course changed the return:
return (
<div>
{ assignments ? assignments.map((assignment) =>
<TeacherAssignmentCard classData={classData} assignmentData={assignment} />) : null}
</div>
);
I thought this was a fairly elegant solution and it worked perfectly. Each tab click only changes the tab for one assignment. However, what was very strange was that I lost the nice smooth MUI animation where the line underneath the tab slides over to the new tab. Instead it just repositions instantly.
I have checked and rewritten my code a half dozen times and made sure it's exactly the same. Any ideas what could be causing this weird behaviour or how to fix it?? Thank you.

Related

React UseRef is undefined using a wrapper

I'm following this example in order to achive dynamically-created elements that can be printed using react To Print.
I have the following code (showing sections to keep this question as clean as possible):
/*This section is loaded after a user has been selected from a select input*/
{rows?.map((row,index) => (
<PrintHojaVacaciones key={index} vacacion={row}/>
))}
const PrintHojaVacaciones = ({vacacion}) => {
const componentRef = useRef();
return (
<div>
{vacacion.id}
<ReactToPrint
trigger={() => {
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}}
content={() => componentRef.current}
/>
<Diseno_PrintHojaVacaciones ref={componentRef} value={vacacion}/>
</div>
)
}
export default PrintHojaVacaciones
const Diseno_PrintHojaVacaciones = React.forwardRef((props,ref) => {
const { value } = props;
return (
<div ref={ref}>{value.aprobadapor}</div>
);
});
export default Diseno_PrintHojaVacaciones;
Thing is, useRef is undefined. I have been trying with CreateRef as well, with the same result. I also tried to "move" my code to the codesandbox above displayed and it works well, but in my own application, it returns undefined. The whole useRef is new to me and I don't understand it well yet, so any help would be appreciated.
The route is being called using Lazy loading from react router (I don't know if this could be the culprit)
I don't know exactly what I did to make it work, I really have no idea, but my Trigger now looks like this and it is now working (not sure if I'm missing something else). The difference is that the function is not inside brackets after the =>.
trigger={() =>
<SqIconButton tip={`Imprimir (Aprobada por ${vacacion.aprobadapor})`}
color={"success"}
disableElevation={true}>
<><BsIcons.BsHandThumbsUpFill/><AiIcons.AiFillPrinter/></>
</SqIconButton>
}

How can I create a new component pressing a Button in React Native?

I'm stuck in a problem, I have to press (onPress) a button and this must create a new component. Just like a to-do list. But, I don't know how can I solve this problem.
There's lots of todo apps you could look at to get a better idea, but generally the solution is a component with a list on state which renders an element for each item in the list.
const Comp = () => {
const [todos, setTodos] = useState(["first"]);
return <>
{todos.map(t => <Row
onPress={() => setTodos([...todos, Math.random().toString()])
>
{t}
</Row>)}
</>;
};

React state showing differing values from each dynamically generated input field component

I've been banging my head against my keyboard for the past 8 hours trying to figure out why each input field is showing different values for the same state.
Here is the code -
const BeaconSettingsCard = (props) => {
const [settingsItems, setSettingsItems] = useState([]);
const handleAddBeaconBtnOnClick = () => {
const id = settingsItems.length;
const newItem = (
<InputItem
id={id}
key={id}
type="InputField"
title="Test Title"
value="Test Value"
onChange={(e) => handleBeaconIdInputFieldOnChange(e, id)}
/>
);
setSettingsItems((settingsItems) => [...settingsItems, newItem]);
};
const handleBeaconIdInputFieldOnChange = (e, id) => {
console.log("settingsItems: ", settingsItems); // each input field shows a different settingsItems value ??
};
let cardHeaderButton = (
<InputItem type="Button" width="150px" onClick={handleAddBeaconBtnOnClick}>
Click to Add
</InputItem>
);
return (
<SettingsCard
headerButton={cardHeaderButton}
settingsItems={settingsItems}
/>
);
};
export default BeaconSettingsCard;
When I log the "settingsItems" state in the onChange event for each input field, I get different values.
On the first dynamically generated inputfield, it logs settingsItems as []. On the second, it logs [{React Component}]. On the third, it logs [{React Component}, {React Component}] and so forth.
These should all be logging the same state value! My friend who is a react wiz couldn't seem to figure this out either. Really hoping someone here can. Thank you.
I solved this strange issue by converting the code from React hooks to a normal React component.
Not ideal, but works. Hopefully if someone runs into a strange issue like this, this solution will work for you too.

Problem with using different onClick events in one button component

I wanted to make my components as reusable as it possible but when I started adding events the problems occured. I am using one button component in a lot of places in my app and I just change its name. It worked fine when I passed one onClick event to it (to change menu button name) but when I wanted to do the same with another button (to change cycle name) and when I passed second onClick event to the same button component the menu button stopped working. I tried to find solution but found only different topics. I know I could make a wrapper around the button and make onClick on the wrapper, but I think I am doing something wrong and there must be more elegant way to handle this.
Button component
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton onClick={changeButtonName, changeCycle}>
{text}
</AppButton>
);
};
Navbar component where cycle and menuu buttons are placed
export const Navbar = () => {
const menuButton = 'Menu';
const closeButton = 'Zamknij';
const [menuButtonName, setMenuButtonName] = useState(menuButton);
const changeButtonName = () => {
menuButtonName === menuButton ? setMenuButtonName(closeButton) : setMenuButtonName(menuButton);
}
const interiorButton = 'Interior →';
const structuralCollageButton = 'Structural Collage →';
const [cycleButtonName, setCycleButtonName] = useState(interiorButton);
const changeCycle = () => {
cycleButtonName === interiorButton ? setCycleButtonName(structuralCollageButton) : setCycleButtonName(interiorButton);
}
return (
<Nav>
<AuthorWrapper>
<AuthorName>
Michał Król
</AuthorName>
<AuthorPseudonym>
Structuralist
</AuthorPseudonym>
</AuthorWrapper>
<CycleButtonWrapper >
<Button text={cycleButtonName} changeCycle={changeCycle} />
</CycleButtonWrapper>
<MenuButtonWrapper>
<Button text={menuButtonName} changeButtonName={changeButtonName} />
</MenuButtonWrapper>
</Nav>
)
}
this is not a really reusable approach for a Button. For every new method name you would have to include in the props params and you could face something like:
export const Button = ({text, changeButtonName, changeCycle, changeTheme, changeDisplay})
the proper way to make it reusable would be by passing only one handler to your button:
export const Button = ({text, clickHandler}) => {
return (
<AppButton onClick={clickHandler}>
{text}
</AppButton>
);
};
fwiw, the reason you have problem is because at this code onClick={changeButtonName, changeCycle} you are passing multiple expressions with comma operator where the last operand is returned.
You cannot pass two functions to onClick. Either do a conditional check that call that function which is passed or make a wrapper function.
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton onClick={changeButtonName || changeCycle}>
{text}
</AppButton>
);
};
or
export const Button = ({text, changeButtonName, changeCycle}) => {
return (
<AppButton
onClick={() => {
changeButtonName && changeButtonName();
changeCycle && changeCycle();
}
}>
{text}
</AppButton>
);
};
update your code like
<AppButton onClick={()=> {
changeButtonName && changeButtonName();
changeCycle && changeCycle();
}}>
{text}
</AppButton>

Load Components Dynamically React Js with load more button

I'm new to React Js, so I can't find a solution to my problem by myself, please help me.
I'm working on a website with a blog page, blogs should be displayed dynamically on the page. When page loads I want it to have 4 blogs, and underneath there will be button, so when the user clicks it, React should render and display the rest of the blogs.
My code so far looks like this:
import { blogs} from "./blogs";
import { Blog} from "./Blog";
function BlogList() {
const cardComponent = blogs.slice(0,6).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>{cardComponent}</div>
)
}`````
**This code lets me display 6 blogs when the page is loaded, what I want to do is add "Load More" button under these already loaded 6 blogs, when the user clicks the button it should render and display another 4 blogs from "blogs", and again have Load More button.** Any help will be greatly appreciated,
Thank you.
Your code shows a fixed amount of blogs (6). Instead of hardcoding the amount of visible blogs, you need to store it in a variable that you can change later. We will use useState for this. You also need to change the amount of posts based on a button press, so a button and an action is also needed.
function BlogList() {
// Starting number of visible blogs
const [visibleBlogs, setVisibleBlogs] = useState(6)
// Set the visible blogs to the current amount + 4
// eg. if there are 10 visible post, clicking again will show 14.
const handleClick = () => {
setVisibleBlogs(prevVisibleBlogs => prevVisibleBlogs + 4)
}
const cardComponent = blogs.slice(0, visibleBlogs).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>
{cardComponent}
<button type="button" onClick={handleClick}>
See more
</button>
</div>
)
}
I hope it helps.
You can do it this way:
function BlogList() {
const [maxRange, setMaxRange] = useState(6);
const loadMore = useCallback(() => {
setMaxRange(prevRange => prevRange + 4);
},[])
const cardComponent = blogs.slice(0, maxRange).map((blog, i) => {
return (
<Blog
key={i}
id={blogs[i].id}
img={blogs[i].img.src}
date={blogs[i].date}
title={blogs[i].title}
img2={blogs[i].img2.src}
logoTitle={blogs[i].logoTitle}
text={blogs[i].text}
/>
);
});
return (
<div>
{cardComponent}
<button onClick={loadMore}>Load More</button>
</div>
)
}
So you can just maintain the maximum number of currently displayed Blogs in state and increment it when the button gets clicked.
I used useCallback so that a new function doesn't get created when the component re-renders.

Resources